﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Practices.ServiceLocation;

namespace SimpleServiceBus.Utilities.ServiceLocator
{
    /// <summary>
    /// Default implementation of IServiceLocator.
    /// </summary>
    public class DefaultServiceLocator : IServiceLocator
    {
        private static readonly Dictionary<string, Type> KeyedTypes;
        private static readonly List<KeyValuePair<Type, object>> Singletons;
        private static readonly object Sync = new object();

        /// <summary>
        /// Initializes the <see cref="DefaultServiceLocator"/> class.
        /// </summary>
        static DefaultServiceLocator()
        {
            lock (Sync)
            {
                KeyedTypes = new Dictionary<string, Type>();
                Singletons = new List<KeyValuePair<Type, object>>();
            }
        }

        #region Implementation of IServiceProvider

        /// <summary>
        /// Gets the service object of the specified type.
        /// </summary>
        /// <returns>
        /// A service object of type <paramref name="serviceType"/>.
        ///                     -or- 
        ///                 null if there is no service object of type <paramref name="serviceType"/>.
        /// </returns>
        /// <param name="serviceType">An object that specifies the type of service object to get. 
        ///                 </param><filterpriority>2</filterpriority>
        public object GetService(Type serviceType)
        {
            return GetInstance(serviceType);
        }

        #endregion

        #region Implementation of IServiceLocator

        /// <summary>
        /// Gets an instance of the specified type. If a singleton instance has not been registered, a default 
        /// instance is created.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <returns></returns>
        public object GetInstance(Type serviceType)
        {
            return GetInstance(serviceType, null);
        }

        /// <summary>
        /// Gets an instance of the specified type. If a singleton instance has not been registered, a default 
        /// instance is created.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <returns></returns>
        public TService GetInstance<TService>()
        {
            return (TService) GetInstance(typeof (TService), null);
        }

        /// <summary>
        /// Gets a keyed instance. If key is null, either a registered singleton instance is returned or one is created. 
        /// Throws an ActivationException if forced to attempt creation of an abstract type.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public TService GetInstance<TService>(string key)
        {
            return (TService) GetInstance(typeof (TService), key);
        }

        /// <summary>
        /// Gets a keyed instance. If key is null, either a registered singleton instance is returned or one is created. 
        /// Throws an ActivationException if forced to attempt creation of an abstract type.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <param name="key">The key.</param>
        /// <returns></returns>
        public object GetInstance(Type serviceType, string key)
        {
            lock (Sync)
            {
                if (!string.IsNullOrEmpty(key) && KeyedTypes.ContainsKey(key)) 
                    return KeyedTypes[key];

                //In a list of singletons, we'll return the default. The default in this locator is the first in the list.
                var pair = Singletons.Where(kvp => kvp.Key == serviceType).FirstOrDefault();
                if (pair.Value != null)
                    return pair.Value;
            }

            if (serviceType.IsInterface || serviceType.IsAbstract) 
                throw new ActivationException("Cannot create instance of abstract type.");

            return Activator.CreateInstance(serviceType);
        }

        /// <summary>
        /// Gets all instances of the type specified. If none exist, an empty IEnumerable<TService> is returned.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <returns></returns>
        public IEnumerable<TService> GetAllInstances<TService>()
        {
            return (from s in GetAllInstances(typeof (TService))
                    select (TService) s).ToArray();
        }

        /// <summary>
        /// Gets all instances of the type specified. If none exist, an empty IEnumerable<object> is returned.
        /// </summary>
        /// <param name="serviceType">Type of the service.</param>
        /// <returns></returns>
        public IEnumerable<object> GetAllInstances(Type serviceType)
        {
            IEnumerable<KeyValuePair<Type, object>> pairs;
            lock (Sync)
            {
                pairs = Singletons.Where(kvp => kvp.Key == serviceType);
            }

            if (pairs.Count() == 0) return new object[0];

            return (from kvp in pairs
                    select kvp.Value).ToArray();
        }

        #endregion

        /// <summary>
        /// Registers an instance, associating it with it's type..
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="instance">The instance.</param>
        public void RegisterInstance<T>(T instance)
        {
            lock (Sync)
            {
                Singletons.Add(new KeyValuePair<Type, object>(typeof(T), instance));
            }
        }
    }
}
